(**  Store module description *)
open Common
open ExtLib
open Dispose
	
exception No_more_data
exception No_more_statements
exception Sqlite3RcException of Sqlite3.Rc.t

type t = Sqlite3.stmt Dispose.disposableContainer
	
let fail_if_error = function
  | Sqlite3.Rc.OK | Sqlite3.Rc.DONE | Sqlite3.Rc.ROW as rc -> rc
	| rc -> raise (Sqlite3RcException rc)

let omit_success_or_fail rc =
  ignore (fail_if_error rc)

let wrap stmt =
	new disposableContainer stmt Sqlite3.finalize

let create_stmt db queryStr =
	let free_stmts = List.iter (fun s -> Sqlite3.finalize s |> ignore) in
	let rec prepare_next ((stmt :: _) as l) =
		try
			match Sqlite3.prepare_tail stmt with
				| Some(newStmt) -> prepare_next (newStmt :: l)
				| None -> (None, l)
		with
			| exn -> Some(exn), l
	in
	let prepare_all firstStmt =
		match prepare_next [firstStmt] with
			| Some(exn), l -> free_stmts l; raise exn
			| _, l -> l
	in
		(Sqlite3.prepare db queryStr) |>
				(prepare_all) |>
						List.rev
		
let create_single_stmt db queryStr =
	match create_stmt db queryStr with
		| s :: [] -> wrap s
		| _ -> assert false
			
let create_multiple_stmt db queryStr =
	new disposableContainer (List.map wrap (create_stmt db queryStr)) (List.iter dispose)

(** Create function which accept stmt and execute it until one of the stopCondition is meet
		@param stopCondition : [Sqlite3.Rc.t array]
		@return [Sqlite3.stmt -> Sqlite3.Rc.t = <fun>]
		@raise Sqlite3.Error .
*)
let execute_stmt stopCondition =
	let rec run stmt =
		let res = (Sqlite3.step (get_value stmt)) |> fail_if_error in
			match Array.exists ((=)res) stopCondition with 
				| false -> run stmt
				| _ -> res
	in
		run

(** Add descr execute_non_query
		@param stmt : [Sqlite3.stmt]
		@raise Sqlite3.Error .
*)
let execute_non_query stmt =
	execute_stmt [|Sqlite3.Rc.DONE|] stmt |> ignore

(** Add descr execute_query_row
		@param stmt : [Sqlite3.stmt]
		@return [Sqlite3.Data.t array]
		@raise No_more_data .
		@raise Sqlite3.Error .
*)
let execute_query_row stmt =
	match execute_stmt [|Sqlite3.Rc.DONE; Sqlite3.Rc.ROW|] stmt with
		| Sqlite3.Rc.ROW -> Some(Sqlite3.row_data (get_value stmt))
		| Sqlite3.Rc.DONE -> None
		| rc ->
				raise (Sqlite3RcException(rc))

(** Add descr execute_query_row
		@param stmt : [Sqlite3.stmt]
		@return [Sqlite3.Data.t array list]
		@raise No_more_data .
		@raise Sqlite3.Error .
*)
let execute_query_dataset stmt =
	let rec read_all acc =
		match execute_query_row stmt with
			| None -> acc
			| Some(row) -> read_all (row :: acc)
	 in
		read_all [] |> List.rev

let execute_scalar stmt =
	match execute_query_row stmt with
		| None -> None
		| Some(row) ->
				if (Array.length row) > 0 then
					Some(row.(0))
				else
					None

let set_parameters stmt params =
	let real_stmt = get_value stmt in
	let stmts_param_count = Sqlite3.bind_parameter_count real_stmt in
		assert (stmts_param_count == (Array.length params));
		Sqlite3.reset real_stmt |> omit_success_or_fail;
		for i = 0 to stmts_param_count - 1 do
			Sqlite3.bind real_stmt (i + 1) params.(i) |> omit_success_or_fail;
		done;
		stmt
						
class squery (stmt: Sqlite3.stmt disposableContainer) = object(self)
	method execute_non_query () =
		execute_non_query stmt;

	method execute_query_row () =
		execute_query_row stmt;
			
	method execute_query_dataset () =
		execute_query_dataset stmt;

	method execute_scalar () =
		execute_scalar stmt

	method dispose () =
		stmt#dispose()

	method as_disposable =
		Some(self:>disposable)
end

	
